package com.mdinic.blogovi.model;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.Table;
import javax.persistence.TypedQuery;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;

@Configurable
@Entity
@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "userId", "providerId", "providerUserId" }) })
public class UserConnection {

    @PersistenceContext
    transient EntityManager entityManager;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @Version
    @Column(name = "version")
    private Integer version;

    @Column
    private String userId;

    @NotNull
    @Column
    private String providerId;

    @Column
    private String providerUserId;

    private String displayName;

    private String profileUrl;

    private String imageUrl;

    @NotNull
    private String accessToken;

    private String secret;

    private String refreshToken;

    private Long expireTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getProviderId() {
        return providerId;
    }

    public void setProviderId(String providerId) {
        this.providerId = providerId;
    }

    public String getProviderUserId() {
        return providerUserId;
    }

    public void setProviderUserId(String providerUserId) {
        this.providerUserId = providerUserId;
    }

    public String getDisplayName() {
        return displayName;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public String getProfileUrl() {
        return profileUrl;
    }

    public void setProfileUrl(String profileUrl) {
        this.profileUrl = profileUrl;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
    }

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public String getRefreshToken() {
        return refreshToken;
    }

    public void setRefreshToken(String refreshToken) {
        this.refreshToken = refreshToken;
    }

    public Long getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(Long expireTime) {
        this.expireTime = expireTime;
    }

    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }

    public static final EntityManager entityManager() {
        EntityManager em = new UserConnection().entityManager;
        if (em == null)
            throw new IllegalStateException(
                    "Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
        return em;
    }

    public static long countUserConnections() {
        return entityManager().createQuery("SELECT COUNT(o) FROM UserConnection o", Long.class).getSingleResult();
    }

    public static List<com.mdinic.blogovi.model.UserConnection> findAllUserConnections() {
        return entityManager().createQuery("SELECT o FROM UserConnection o", UserConnection.class).getResultList();
    }

    public static com.mdinic.blogovi.model.UserConnection findUserConnection(Long id) {
        if (id == null)
            return null;
        return entityManager().find(UserConnection.class, id);
    }

    public static List<com.mdinic.blogovi.model.UserConnection> findUserConnectionEntries(int firstResult,
            int maxResults) {
        return entityManager().createQuery("SELECT o FROM UserConnection o", UserConnection.class)
                .setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
    }

    @Transactional
    public void persist() {
        if (this.entityManager == null)
            this.entityManager = entityManager();
        this.entityManager.persist(this);
    }

    @Transactional
    public void remove() {
        if (this.entityManager == null)
            this.entityManager = entityManager();
        if (this.entityManager.contains(this)) {
            this.entityManager.remove(this);
        } else {
            UserConnection attached = UserConnection.findUserConnection(this.id);
            this.entityManager.remove(attached);
        }
    }

    @Transactional
    public void flush() {
        if (this.entityManager == null)
            this.entityManager = entityManager();
        this.entityManager.flush();
    }

    @Transactional
    public void clear() {
        if (this.entityManager == null)
            this.entityManager = entityManager();
        this.entityManager.clear();
    }

    @Transactional
    public com.mdinic.blogovi.model.UserConnection merge() {
        if (this.entityManager == null)
            this.entityManager = entityManager();
        UserConnection merged = this.entityManager.merge(this);
        this.entityManager.flush();
        return merged;
    }

    public static TypedQuery<UserConnection> findUserConnectionsByProviderUserIdEquals(String providerUserId) {
        if (providerUserId == null || providerUserId.length() == 0)
            throw new IllegalArgumentException("The providerUserId argument is required");
        EntityManager em = UserConnection.entityManager();
        TypedQuery<UserConnection> q = em.createQuery(
                "SELECT o FROM UserConnection AS o WHERE o.providerUserId = :providerUserId", UserConnection.class);
        q.setParameter("providerUserId", providerUserId);
        return q;
    }

    public static TypedQuery<UserConnection> findUserConnectionByUserIdProviderUserId(String userId,
            String providerUserId) {
        if (providerUserId == null || providerUserId.length() == 0)
            throw new IllegalArgumentException("The providerUserId argument is required");
        if (userId == null || userId.length() == 0)
            throw new IllegalArgumentException("The userId argument is required");

        EntityManager em = UserConnection.entityManager();
        TypedQuery<UserConnection> q = em.createQuery(
                "SELECT o FROM UserConnection AS o WHERE o.providerUserId = :providerUserId AND o.userId = :userId",
                UserConnection.class);
        q.setParameter("providerUserId", providerUserId);
        q.setParameter("userId", userId);

        return q;
    }

    public static void deleteByUserIdProviderUserId(String userId, String providerUserId) {
        if (providerUserId == null || providerUserId.length() == 0)
            throw new IllegalArgumentException("The providerUserId argument is required");
        if (userId == null || userId.length() == 0)
            throw new IllegalArgumentException("The userId argument is required");

        EntityManager em = UserConnection.entityManager();
        Query q = em
                .createQuery("DELETE FROM UserConnection WHERE o.providerUserId = :providerUserId AND o.userId = :userId");

        q.setParameter("providerUserId", providerUserId);
        q.setParameter("userId", userId);
        q.executeUpdate();
    }

    public static List<String> findUserConnectionByProviderIdAndProviderUserId(String providerId, String providerUserId) {
        if (providerUserId == null || providerUserId.length() == 0)
            throw new IllegalArgumentException("The providerUserId argument is required");
        if (providerId == null || providerId.length() == 0)
            throw new IllegalArgumentException("The providerId argument is required");

        EntityManager em = UserConnection.entityManager();
        TypedQuery<String> q = em
                .createQuery(
                        "SELECT o.userId FROM UserConnection AS o WHERE o.providerUserId = :providerUserId AND o.providerId = :providerId",
                        String.class);
        q.setParameter("providerUserId", providerUserId);
        q.setParameter("providerId", providerId);
        return q.getResultList();
    }

    public static List<UserConnection> findUserConnectionsByUserId(String userId) {
        if (userId == null || userId.length() == 0)
            throw new IllegalArgumentException("The userId argument is required");
        EntityManager em = UserConnection.entityManager();
        TypedQuery<UserConnection> q = em.createQuery("SELECT o FROM UserConnection AS o WHERE o.userId = :userId",
                UserConnection.class);
        q.setParameter("userId", userId);
        return q.getResultList();
    }

    public static List<String> findByProviderIdAndProviderUserIds(String providerId, Set<String> providerUserIds) {
        if (providerId == null || providerId.length() == 0)
            throw new IllegalArgumentException("The providerId argument is required");

        EntityManager em = UserConnection.entityManager();
        TypedQuery<String> q = em
                .createQuery(
                        "SELECT o.userId FROM UserConnection AS o WHERE o.providerId = :providerId AND providerUserId in (:providerUserIds)",
                        String.class);
        q.setParameter("providerId", providerId);
        q.setParameter("providerUserIds", providerUserIds);
        return q.getResultList();
    }

    public static List<UserConnection> findUserConnectionByUserIdProviderId(String userId, String providerId) {
        if (userId == null || userId.length() == 0)
            throw new IllegalArgumentException("The userId argument is required");
        if (providerId == null || providerId.length() == 0)
            throw new IllegalArgumentException("The providerId argument is required");

        EntityManager em = UserConnection.entityManager();
        TypedQuery<UserConnection> q = em.createQuery(
                "SELECT o FROM UserConnection AS o WHERE o.providerId = :providerId AND userId = :userId",
                UserConnection.class);
        q.setParameter("providerId", providerId);
        q.setParameter("userId", userId);
        return q.getResultList();
    }

    public static List<UserConnection> findByUserIdAndProviders(String userId,
            MultiValueMap<String, String> providerUsers) {
        if (userId == null || userId.isEmpty()) {
            throw new IllegalArgumentException("Unable to execute find: no userId provided");
        }

        if (providerUsers == null || providerUsers.isEmpty()) {
            throw new IllegalArgumentException("Unable to execute find: no providerUsers provided");
        }

        StringBuilder providerUsersCriteriaSql = new StringBuilder();
        MapSqlParameterSource parameters = new MapSqlParameterSource();
        parameters.addValue("userId", userId);
        for (Iterator<Entry<String, List<String>>> it = providerUsers.entrySet().iterator(); it.hasNext();) {
            Entry<String, List<String>> entry = it.next();
            String providerId = entry.getKey();
            providerUsersCriteriaSql.append("providerId = :providerId_").append(providerId)
                    .append(" and providerUserId in (:providerUserIds_").append(providerId).append(")");
            parameters.addValue("providerId_" + providerId, providerId);
            parameters.addValue("providerUserIds_" + providerId, entry.getValue());
            if (it.hasNext()) {
                providerUsersCriteriaSql.append(" or ");
            }
        }

        EntityManager em = UserConnection.entityManager();
        TypedQuery<UserConnection> q = em.createQuery(
                "SELECT o FROM UserConnection AS o WHERE o.userId = :userId AND userId = :userId AND "
                        + providerUsersCriteriaSql, UserConnection.class);
        q.setParameter("userId", userId);

        Map<String, Object> values = parameters.getValues();
        Set<Entry<String, Object>> entrySet = values.entrySet();
        for (Entry<String, Object> entry : entrySet) {
            q.setParameter(entry.getKey(), entry.getValue());
        }

        return q.getResultList();
    }

    public static UserConnection findByUserIdAndProviderIdAndProviderUserId(String userId, String providerId,
            String providerUserId) {
        if (userId == null || userId.length() == 0)
            throw new IllegalArgumentException("The userId argument is required");
        if (providerId == null || providerId.length() == 0)
            throw new IllegalArgumentException("The providerId argument is required");
        if (providerUserId == null || providerUserId.length() == 0)
            throw new IllegalArgumentException("The providerUserId argument is required");

        EntityManager em = UserConnection.entityManager();
        TypedQuery<UserConnection> q = em
                .createQuery(
                        "SELECT o FROM UserConnection AS o WHERE o.providerId = :providerId AND userId = :userId AND providerUserId = :providerUserId",
                        UserConnection.class);
        q.setParameter("providerId", providerId);
        q.setParameter("userId", userId);
        q.setParameter("providerUserId", providerUserId);

        return q.getSingleResult();
    }

    public static void deleteByUserIdAndProviderIdAndProviderUserId(String userId, String providerId,
            String providerUserId) {
        if (userId == null || userId.length() == 0)
            throw new IllegalArgumentException("The userId argument is required");
        if (providerId == null || providerId.length() == 0)
            throw new IllegalArgumentException("The providerId argument is required");
        if (providerUserId == null || providerUserId.length() == 0)
            throw new IllegalArgumentException("The providerId argument is required");

        EntityManager em = UserConnection.entityManager();
        Query q = em
                .createQuery("SELECT o FROM UserConnection AS o WHERE o.providerId = :providerId AND userId = :userId AND providerUserId = :providerUserId");
        q.setParameter("providerId", providerId);
        q.setParameter("userId", userId);
        q.setParameter("providerUserId", providerUserId);

        q.executeUpdate();
    }
}
